﻿using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using Pfz.WpfControls.TypeConverters;
using System.Windows.Threading;

namespace Pfz.WpfControls
{
	/// <summary>
	/// This grid considers columns widths (Auto is considered *), but also puts each element in the next cell automatically,
	/// so you don't need to create many row definitions or to set Grid.Column and Grid.Row properties.
	/// </summary>
	public sealed class WrapGrid:
		Panel
	{
		private double[] _finalWidths;
		private double[] _finalHeights;

		/// <summary>
		/// Create a new WrapGrid instance.
		/// </summary>
		public WrapGrid()
		{
			_columnWidths.CollectionChanged += (a, b) => InvalidateMeasure();
			_rowHeights.CollectionChanged += (a, b) => InvalidateMeasure();
		}

		private ObservableCollection<GridLength> _columnWidths = new ObservableCollection<GridLength>();
		/// <summary>
		/// Gets the Widths collection.
		/// </summary>
		[TypeConverter(typeof(ObservableLengthsConverter))]
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
		public ObservableCollection<GridLength> ColumnWidths
		{
			get
			{
				return _columnWidths;
			}
			set
			{
				_columnWidths.Clear();

				if (value != null)
					foreach(var width in value)
						_columnWidths.Add(width);
			}
		}

		private ObservableCollection<GridLength> _rowHeights = new ObservableCollection<GridLength>();
		/// <summary>
		/// Sets the heights of the rows in this grid.
		/// If this is not set, all rows are auto. If there are rows set, but there are more physical rows than set here
		/// the definition of the last rows applies to all others.
		/// </summary>
		[TypeConverter(typeof(ObservableLengthsConverter))]
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
		public ObservableCollection<GridLength> RowHeights
		{
			get
			{
				return _rowHeights;
			}
			set
			{
				_rowHeights.Clear();

				if (value != null)
					foreach(var height in value)
						_rowHeights.Add(height);
			}
		}

		/// <summary>
		/// Measures the size of each child and its own.
		/// </summary>
		protected override Size MeasureOverride(Size availableSize)
		{
			var children = Children;
			int childCount = children.Count;
			if (childCount == 0)
				return base.MeasureOverride(availableSize);

			int columnCount = _columnWidths.Count;
			int expectedColumnCount = columnCount;
			int rowCount = 1;
			if (columnCount > 0)
				rowCount = (childCount + columnCount - 1) / columnCount;
			else
				expectedColumnCount = childCount;

			foreach(UIElement child in children)
				child.Measure(availableSize);

			_GetColumnWidths(columnCount, availableSize.Width);
			_GetRowHeights(rowCount, expectedColumnCount, availableSize.Height);

			double finalWidth = 0;
			foreach(double width in _finalWidths)
				finalWidth += width;

			double finalHeight = 0;
			foreach(double height in _finalHeights)
				finalHeight += height;

			return new Size(finalWidth, finalHeight);
		}

		private void _GetColumnWidths(int realColumnCount, double availableWidth)
		{
			var children = Children;
			int childCount = children.Count;
			int expectedColumnCount = realColumnCount;
			if (expectedColumnCount == 0)
				expectedColumnCount = childCount;

			if (_finalWidths == null || _finalWidths.Length != expectedColumnCount)
				_finalWidths = new double[expectedColumnCount];

			double totalAbsolute = 0;
			double totalStar = 0;
			for(int i=0; i<expectedColumnCount; i++)
			{
				GridLength length = new GridLength();

				if (i < realColumnCount)
					length = _columnWidths[i];

				if (length.IsStar)
				{
					totalStar += length.Value;
					continue;
				}

				if (length.IsAbsolute)
				{
					double lengthValue = length.Value;

					for(int childIndex=i; childIndex<childCount; childIndex += expectedColumnCount)
					{
						var child = children[childIndex] as FrameworkElement;
						if (child != null)
							lengthValue = Math.Max(lengthValue, child.MinWidth);
					}

					totalAbsolute += lengthValue;
					_finalWidths[i] = lengthValue;
					availableWidth -= lengthValue;
					continue;
				}

				double maxWidth = 0;
				for(int childIndex=i; childIndex<childCount; childIndex += expectedColumnCount)
				{
					var child = children[childIndex];
					maxWidth = Math.Max(child.DesiredSize.Width, maxWidth);

					var frameworkElement = child as FrameworkElement;
					if (frameworkElement != null)
						maxWidth = Math.Max(maxWidth, frameworkElement.MinWidth);
				}

				totalAbsolute += maxWidth;
				_finalWidths[i] = maxWidth;
				availableWidth -= maxWidth;
			}

			if (totalStar > 0)
			{
				double remainingWidth = availableWidth;
				if (remainingWidth < 0)
					remainingWidth = 0;

				for(int i=0; i<expectedColumnCount; i++)
				{
					var length = _columnWidths[i];
					if (!length.IsStar)
						continue;

					double width = length.Value * remainingWidth / totalStar;
					for(int childIndex=i; childIndex<childCount; childIndex += expectedColumnCount)
					{
						var child = children[childIndex] as FrameworkElement;
						if (child != null)
							width = Math.Max(width, child.MinWidth);
					}
					_finalWidths[i] = width;
				}
			}
		}

		private void _GetRowHeights(int rowCount, int expectedColumnCount, double availableHeight)
		{
			var children = Children;
			var childCount = children.Count;
			int rowHeightCount = _rowHeights.Count;

			double totalStar = 0;
			double totalAbsolute = 0;

			if (_finalHeights == null || _finalHeights.Length != rowCount)
				_finalHeights = new double[rowCount];

			for(int rowIndex=0; rowIndex<rowCount; rowIndex++)
			{
				GridLength length = new GridLength();
				if (rowIndex < rowHeightCount)
					length = _rowHeights[rowIndex];
				else
				if (rowHeightCount > 0)
					length = _rowHeights[rowHeightCount-1];

				if (length.IsStar)
				{
					totalStar += length.Value;
					continue;
				}

				int rowStart = rowIndex * expectedColumnCount;
				int rowEnd = rowStart + expectedColumnCount;
				if (rowEnd > childCount)
					rowEnd = childCount;

				if (length.IsAbsolute)
				{
					double lengthValue = length.Value;
					for(int childIndex = rowStart; childIndex < rowEnd; childIndex++)
					{
						var frameworkElement = children[childIndex] as FrameworkElement;
						if (frameworkElement != null)
							lengthValue = Math.Max(lengthValue, frameworkElement.MinHeight);
					}

					totalAbsolute += lengthValue;
					_finalHeights[rowIndex] = lengthValue;
					availableHeight -= lengthValue;
					continue;
				}

				double maxHeight = 0;
				for(int childIndex = rowStart; childIndex < rowEnd; childIndex++)
				{
					var child = children[childIndex];
					maxHeight = Math.Max(maxHeight, child.DesiredSize.Height);

					var frameworkElement = child as FrameworkElement;
					if (frameworkElement != null)
						maxHeight = Math.Max(maxHeight, frameworkElement.MinHeight);
				}

				totalAbsolute += maxHeight;
				_finalHeights[rowIndex] = maxHeight;
				availableHeight -= maxHeight;
			}

			if (totalStar > 0)
			{
				double remainingHeight = availableHeight;
				if (remainingHeight < 0 || remainingHeight == double.PositiveInfinity)
					remainingHeight = 0;

				for(int i=0; i<rowCount; i++)
				{
					GridLength length = new GridLength();
					if (i < rowHeightCount)
						length = _rowHeights[i];
					else
					if (rowHeightCount > 0)
						length = _rowHeights[rowHeightCount-1];

					if (!length.IsStar)
						continue;

					double height = length.Value * remainingHeight / totalStar;

					int rowStart = i * expectedColumnCount;
					int rowEnd = rowStart + expectedColumnCount;
					if (rowEnd > childCount)
						rowEnd = childCount;

					for(int childIndex=rowStart; childIndex<rowEnd; childIndex++)
					{
						var child = children[childIndex] as FrameworkElement;
						if (child != null)
							height = Math.Max(height, child.MinHeight);
					}

					_finalHeights[i] = height;
				}
			}
		}

		/// <summary>
		/// Positions the children.
		/// </summary>
		protected override Size ArrangeOverride(Size finalSize)
		{
			var children = Children;
			int childCount = children.Count;
			if (childCount == 0)
				return base.ArrangeOverride(finalSize);

			int columnCount = _columnWidths.Count;
			int rowCount = 1;
			int realColumnCount = columnCount;
			if (columnCount > 0)
				rowCount = (Children.Count + columnCount - 1) / columnCount;
			else
				realColumnCount = childCount;

			_GetColumnWidths(columnCount, finalSize.Width);
			_GetRowHeights(rowCount, realColumnCount, finalSize.Height);

			int columnIndex = realColumnCount-1;
			int rowIndex = -1;
			double left = 0;
			double top = 0;
			foreach(UIElement child in children)
			{
				columnIndex++;
				if (columnIndex == realColumnCount)
				{
					columnIndex = 0;

					if (rowIndex >= 0)
						top += _finalHeights[rowIndex];

					rowIndex++;
					left = 0;
				}

				double width = _finalWidths[columnIndex];
				double height = _finalHeights[rowIndex];
				child.Arrange(new Rect(left, top, width, height));
				left += width;
			}

			double finalWidth = 0;
			foreach(double width in _finalWidths)
				finalWidth += width;

			double finalHeight = 0;
			foreach(double height in _finalHeights)
				finalHeight += height;

			return new Size(finalWidth, finalHeight);
		}
	}
}
